package com.baomibing.flow.transfer;

import com.alibaba.fastjson.JSON;
import com.baomibing.core.exception.ServerRuntimeException;
import com.baomibing.flow.constant.*;
import com.baomibing.flow.enginee.Process;
import com.baomibing.flow.enginee.*;
import com.baomibing.flow.exception.FlowExceptionEnum;
import com.baomibing.flow.express.Expression;
import com.baomibing.flow.model.*;
import com.baomibing.flow.ui.*;
import com.baomibing.tool.constant.Strings;
import com.baomibing.tool.util.Checker;
import com.baomibing.tool.util.ObjectUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import io.vavr.collection.HashMap;
import org.apache.commons.lang3.EnumUtils;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * UIFlowTransfer
 *
 * @author frog 2025/2/28 17:08
 * @version 1.0.0
 **/
public class UIFlowTransfer {

    private static void assign(WorkNode workNode, UBaseNode node) {

        workNode.setDescription(node.getData().getDescription()).setIcon(node.getData().getIcon()).setPosition(node.getPosition())
                .setMeasured(node.getMeasured()).setVariables(node.getData().getVariables()).setLpaths(node.getData().getLpaths())
                .setRpaths(node.getData().getRpaths()).setDeleteable(node.getData().getDeleteable()).setSelected(node.getData().getSelected())
                .setDragging(node.getData().getDragging()).setNodeId(node.getId());
    }

    public static WorkFlow transfer2WorkFlow(UWorkFlow uworkFlow) {
        WorkFlow flow = new WorkFlow();
        List<UEdge> edges = uworkFlow.getEdges();
        List<UBaseNode> nodes = uworkFlow.getNodes();
        StartNode start = null;
        EndNode end = null;
        Map<String, BaseNode> nodeMap = Maps.newHashMap();
        List<Process> processes = Lists.newArrayList();
        for (UBaseNode node : nodes) {
            NodeTypeEnum type = NodeTypeEnum.valueOf(node.getType());
            if (type == NodeTypeEnum.Start) {
                start = new StartNode(node.getData().getTitle());
                start.setFlow(flow);
                assign(start, node);
                nodeMap.put(start.getNodeId(), start);
            } else if (type == NodeTypeEnum.End) {
                end = new EndNode(node.getData().getTitle());
                end.setFlow(flow);
                assign(end, node);
                nodeMap.put(end.getNodeId(), end);
            } else if (type == NodeTypeEnum.Fork) {
                ForkNode fork = new ForkNode(node.getData().getTitle());
                fork.setFlow(flow);
                assign(fork, node);
                fork.setModel(ForkModelEnum.valueOf(node.getData().getModel()));
                nodeMap.put(fork.getNodeId(), fork);
            } else  if (type == NodeTypeEnum.Join) {
                JoinNode join = new JoinNode(node.getData().getTitle());
                join.setFlow(flow);
                assign(join, node);
                join.setExeType(JoinExeTypeEnum.valueOf(node.getData().getExeType()));
                nodeMap.put(join.getNodeId(), join);
            } else if (type == NodeTypeEnum.Parallel) {
                ParallelNode parallel = new ParallelNode(node.getData().getTitle());
                parallel.setFlow(flow);
                assign(parallel, node);
                parallel.setModel(ParallelModelEnum.valueOf(node.getData().getModel()))
                        .setExeType(ParallelExeTypeEnum.valueOf(node.getData().getExeType()));
                nodeMap.put(parallel.getNodeId(), parallel);
            } else if (type == NodeTypeEnum.Task) {
                TaskNode task = new TaskNode(node.getData().getTitle());
                task.setFlow(flow);
                assign(task, node);
                task.setModel(TaskModelEnum.valueOf(node.getData().getModel())).setEntrustType(EntrustTypeEnum.valueOf(node.getData().getEntrustType()))
                        .setAllActionRejectType(AllActionRejectTypeEnum.valueOf(node.getData().getAllActionRejectType()))
                        .setRejectPolicy(RejectPolicyEnum.valueOf(node.getData().getRejectPolicy())).setRecommitPolicy(RecommitPolicyEnum.valueOf(node.getData().getRecommitPolicy()))
                        .withEntrusts(ObjectUtil.defaultIfNull(node.getData().getEntrusts(), Lists.newArrayList())).setActionType(ActionTypeEnum.valueOf(node.getData().getAction()));
                nodeMap.put(task.getNodeId(), task);
                if ((ActionTypeEnum.ALL.equals(task.getActionType()) || ActionTypeEnum.ALL_ORDER.equals(task.getActionType())) && Checker.beNotEmpty(task.getEntrusts())) {
                    JoinNode tempJoin = new JoinNode(NodeTypeEnum.Join.name() + Strings.UNDERSCORE + task.getName());
                    tempJoin.setExeType(JoinExeTypeEnum.ALL);
                    List<SubProcess> innerProcesses = Lists.newArrayList();
                    for (String entrust : task.getEntrusts()) {
                        TransitionNode t = new TransitionNode(task, tempJoin, false, false);
                        SubProcess sub = new SubProcess(flow).source(task).actor(entrust).target(tempJoin).action(t);
                        innerProcesses.add(sub);
                    }
                    TaskJoinProcess taskJoinProcess = new TaskJoinProcess(flow);
                    taskJoinProcess.setProcesses(innerProcesses);
                    task.setChildProcess(taskJoinProcess);
                }
            }
        }

        Map<String, UPath> pathMap = nodeMap.values().stream().filter(n -> n instanceof ForkNode).flatMap(n -> ((ForkNode) n).getRpaths().stream()).collect(Collectors.toMap(UPath::getId, Function.identity()));

        Map<String, SubProcess> subProcessMap = Maps.newHashMap();
        for (UEdge edge : edges) {

            WorkNode source = (WorkNode) nodeMap.get(edge.getSource());
            WorkNode target = (WorkNode) nodeMap.get(edge.getTarget());
            // 分支节点 并行节点 聚合节点不加入列表
            Boolean beParallelNode = source instanceof ParallelNode;
            Boolean beForkNode = source instanceof ForkNode;
            Boolean beJoinNode = target instanceof JoinNode || source instanceof JoinNode;

            TransitionNode transition = new TransitionNode(source, target);
            nodeMap.put(transition.getName(), transition);
            transition.setSourceHandle(edge.getSourceHandle()).setTargetHandle(edge.getTargetHandle()).setNodeId(edge.getId());
            if (beForkNode) {
                transition.setExpression(new Expression(ObjectUtil.defaultIfNull(pathMap.get(edge.getSourceHandle()).getExpress(), Strings.EMPTY)));
            }
            SubProcess sub = new SubProcess(flow).source(source).target(target).action(transition);
            subProcessMap.put(source.getName() + Strings.UNDERSCORE + target.getName(), sub);

            if (!(beParallelNode || beForkNode || beJoinNode)) {
                processes.add(sub);
            }
        }

        HashMap<String, SubProcess> filterMap = HashMap.ofAll(subProcessMap);

        for(Map.Entry<String, BaseNode> entry : nodeMap.entrySet()) {
            BaseNode value = entry.getValue();

            if (value instanceof  ParallelNode) { //并行节点处理 source节点为并行节点
                ParallelProcess pp = new ParallelProcess(flow).source((WorkNode) value);
                List<Process> processList = Lists.newArrayList(filterMap.filterKeys(k -> k.startsWith(value.getName())).values());
                pp.setProcesses(processList);
                processes.add(pp);
            } else if (value instanceof ForkNode) {//Fork节点处理 source节点为Fork节点
                ForkProcess fp = new ForkProcess(flow, (ForkNode) value);
                fp.setProcesses(Lists.newArrayList(filterMap.filterKeys(k -> k.startsWith(value.getName())).values()));
                processes.add(fp);
            } else if (value instanceof JoinNode) {
                JoinProcess jp = new JoinProcess(flow);
                SubProcess targetProcess = filterMap.filterKeys(k -> k.startsWith(value.getName())).values().getOrNull();
                jp.setTargetProcess(targetProcess);
                List<SubProcess> joinSubProcess = Lists.newArrayList(filterMap.filterKeys(k -> k.lastIndexOf(value.getName()) > 0).values());
                jp.setProcesses(joinSubProcess);
                processes.add(jp);
            }
        }
        flow.setProcesses(processes).setViewPort(uworkFlow.getViewport()).setNodeMap(nodeMap);
        flow.setStart(start).setEnd(end);
        return flow;
    }

    private static void deassign(UBaseNode node, WorkNode workNode) {
        node.setMeasured(workNode.getMeasured()).setPosition(workNode.getPosition()).setId(workNode.getNodeId());
        node.setType(workNode.getNodeType()).setData(new UData().setTitle(workNode.getName()).setDescription(workNode.getDescription()).setIcon(workNode.getIcon())
                .setLpaths(workNode.getLpaths()).setRpaths(workNode.getRpaths()).setDeleteable(workNode.getDeleteable()).setSelected(workNode.getSelected())
                .setDragging(workNode.getDragging()).setVariables(workNode.getVariables()).setPassed(workNode.getPassed()));
    }

    private static UNode deserializeNode(BaseNode node) {
        NodeTypeEnum nt = EnumUtils.getEnum(NodeTypeEnum.class, node.getNodeType());
        UNode unode = null;
        UBaseNode baseNode = null;
        switch (nt) {
            case Start:
                baseNode  = new UBaseNode();
                deassign(baseNode, (StartNode) node);
                unode = baseNode;
                break;
            case End:
                baseNode = new UBaseNode();
                deassign(baseNode, (EndNode) node);
                unode = baseNode;
                break;
            case Fork:
                baseNode = new UBaseNode();
                deassign(baseNode, (ForkNode) node);
                baseNode.getData().setModel(((ForkNode) node).getModel().name());
                unode = baseNode;
                break;
            case Parallel:
                baseNode = new UBaseNode();
                deassign(baseNode, (ParallelNode) node);
                baseNode.getData().setModel(((ParallelNode) node).getModel().name()).setExeType(((ParallelNode) node).getExeType().name());
                unode = baseNode;
                break;
            case Join:
                baseNode = new UBaseNode();
                deassign(baseNode, (JoinNode) node);
                baseNode.getData().setExeType(((JoinNode) node).getExeType().name());
                unode = baseNode;
                break;
            case Transit:
                UEdge edge = new UEdge();
                TransitionNode transition = (TransitionNode) node;
                edge.setId(transition.getNodeId());
                if (Boolean.TRUE.equals(transition.getPassed())) {
                    edge.setType("passed");
                } else {
                    edge.setType("deletable");
                }
                edge.setSource(transition.getSource().getNodeId()).setTarget(transition.getTarget().getNodeId())
                        .setSourceHandle(transition.getSourceHandle()).setTargetHandle(transition.getTargetHandle()).setPassed(transition.getPassed());
                unode = edge;
                break;
            case Task:
                baseNode = new UBaseNode();
                deassign(baseNode, (TaskNode) node);
                baseNode.getData().setModel(((TaskNode) node).getModel().name())
                        .setAllActionRejectType(((TaskNode) node).getAllActionRejectType().name())
                        .setRecommitPolicy(((TaskNode) node).getRecommitPolicy().name())
                        .setRejectPolicy(((TaskNode) node).getRejectPolicy().name())
                        .setAction(((TaskNode) node).getActionType().name())
                        .setEntrusts(((TaskNode) node).getEntrusts())
                        .setEntrustType(((TaskNode) node).getEntrustType().name());
                unode = baseNode;
                break;
            default:
                throw new ServerRuntimeException(FlowExceptionEnum.NOT_SUPPORT_THE_TYPE_OF_WORK_NODE, nt.name());
        }

        return unode;
    }

    private static void putIfNotExist(Map<String, UNode> map, BaseNode node) {
        map.computeIfAbsent(node.getName(), k -> deserializeNode(node));
    }
    private static void deserializeSubProcess(Map<String, UNode> nodeMap, SubProcess subProcess) {
        WorkNode source = subProcess.getSource();
        WorkNode target = subProcess.getTarget();
        TransitionNode transition = subProcess.getAction();
        putIfNotExist(nodeMap, source);
        putIfNotExist(nodeMap, target);
        putIfNotExist(nodeMap, transition);
    }

    private static void deserializeForkProcess(Map<String, UNode> nodeMap, ForkProcess forkProcess) {
        ForkNode node = forkProcess.getNode();
        putIfNotExist(nodeMap, node);
        deserializeProcess(nodeMap, forkProcess.getProcesses());
    }

    private static void deserializeJoinProcess(Map<String, UNode> nodeMap, JoinProcess joinProcess) {
        deserializeSubProcess(nodeMap, joinProcess.getTargetProcess());
        for (SubProcess subProcess : joinProcess.getProcesses()) {
            deserializeSubProcess(nodeMap, subProcess);
        }
    }

    private static void deserializeParallelProcess(Map<String, UNode> nodeMap, ParallelProcess parallelProcess) {
        putIfNotExist(nodeMap, parallelProcess.getSource());
        deserializeProcess(nodeMap, parallelProcess.getProcesses());
    }



    private static void deserializeProcess(Map<String, UNode> nodeMap, List<Process> processes) {
        for (Process process : processes) {
            if (process instanceof SubProcess) {
                SubProcess sub = (SubProcess) process;
                deserializeSubProcess(nodeMap, sub);
            } else if (process instanceof ForkProcess) {
                deserializeForkProcess(nodeMap, (ForkProcess) process);
            } else if (process instanceof JoinProcess) {
                deserializeJoinProcess(nodeMap, (JoinProcess) process);
            } else if (process instanceof ParallelProcess) {
                deserializeParallelProcess(nodeMap, (ParallelProcess) process);
            }
        }
    }

    public static UWorkFlow transfer2UWorkFlow(WorkFlow flow) {
        UWorkFlow uworkFlow = new UWorkFlow();
        uworkFlow.setViewport(flow.getViewPort());

        List<UBaseNode> nodes = Lists.newArrayList();
        List<UEdge> edges = Lists.newArrayList();
        Map<String, UNode> nodeMap = Maps.newHashMap();
        deserializeProcess(nodeMap, flow.getProcesses());

        for (UNode node : nodeMap.values()) {
            if (node instanceof UBaseNode) {
                nodes.add((UBaseNode) node);
            } else if (node instanceof UEdge) {
                edges.add((UEdge) node);
            }
        }
        uworkFlow.setNodes(nodes).setEdges(edges);
        return uworkFlow;
    }

    public static void main(String[] args) throws IOException {
        String json = new String(Files.readAllBytes(Paths.get("/Users/frog/Downloads/data.json")));
        UWorkFlow flow = JSON.parseObject(json, UWorkFlow.class);
        System.out.println(JSON.toJSONString(flow));
        System.out.println(JSON.toJSONString(transfer2UWorkFlow(UIFlowTransfer.transfer2WorkFlow(flow))));
    }


}
